package com.googlecode.dgwt.client.dojo.data.base;

import java.util.Collection;
import java.util.Set;

import com.googlecode.dgwt.client.dijit.View;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.data.listener.AttachListener;
import com.googlecode.dgwt.client.dojo.data.listener.ModelListener;
import com.googlecode.dgwt.client.dojo.data.listener.StoreListener;
import com.googlecode.dgwt.client.dojo.data.model.StoreColumn;
import com.googlecode.dgwt.client.dojo.data.model.StoreModel;
import com.googlecode.dgwt.client.dojo.exception.AttachException;
import com.googlecode.dgwt.client.dojo.exception.DojoRuntimeException;
import com.googlecode.dgwt.client.dojo.exception.IncompatibleTypesException;
import com.googlecode.dgwt.client.dojo.exception.ValidationException;
import com.googlecode.dgwt.client.dojo.util.SharedList;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class ModeledStoreBase<Item,Model extends StoreModel<Item,Column>,Column extends StoreColumn<Item, ?>> extends IndexedStoreBase<Item> implements ModeledStore<Item> {
    
    private StoreListenerList<Item,ModelListener<Item>> modelListenerList;
    private SharedList<View> widgetList;
    private Model model;
    
    
    protected ModeledStoreBase() {
        super();
    }

    protected ModeledStoreBase(Collection<? extends Item> items) {
        super(items);
    }

    protected ModeledStoreBase(int initialCapacity) {
        super(initialCapacity);
    }

    protected ModeledStoreBase(ModeledStoreBase<Item,Model,Column> mirroredStore, Model newModel) {
        super(mirroredStore);
        setModel(newModel);
    }

    
    @Override
    protected void initialize(){
        modelListenerList = new StoreListenerList<Item, ModelListener<Item>>(this);
        modelListenerList.setAssociatedListenerList(getEngine().getDataListenerList());
    }
    
    public ModeledStoreBase(Model model) {
        super();
        assert (model != null) : Dojo.reflect.getSimpleName(getClass()) + ": model is NULL";
        setModel(model);
    }
    
    public ModeledStoreBase(Model model, Collection<? extends Item> items) {
        super(items);
        setModel(model);
    }
    
    public ModeledStoreBase(Model model, int initialCapacity) {
        super(initialCapacity);
        setModel(model);
    }

    
    protected void setModel(Model model) {
        if (this.model != model) {
            castModel(model);
            
            if (model != null) {
                try {
                    model.onAttach(this);
                } catch (AttachException e) {
                    throw new DojoRuntimeException(e);
                }
            }

            if (this.model != null) {
                if (widgetList != null){
                    for (View view : widgetList){
                        this.model.onRegisterParent(view, false);
                    }
                }
                this.model.onDetach(this);
            }
            
            this.model = model;

            if (this.model != null 
                    && widgetList != null) {
                for (View view : widgetList){
                    this.model.onRegisterParent(view, true);
                }
            }
        }
    }


    @SuppressWarnings("unchecked")
    protected Model castModel(StoreModel<?,?> model) {
        if (!isModelCompatible(model)) {
            if (model == null) {
                throw new IncompatibleTypesException(this, null, getModelBaseClass());
            }
            throw new IncompatibleTypesException(this, model.getClass(), getModelBaseClass());
        }
        return (Model) model;
    }
    
    
    protected boolean isModelCompatible(StoreModel<?, ?> model) {
        return model != null && Dojo.reflect.isSuperClass(getModelBaseClass(), model.getClass());
    }
    
    
    protected abstract Class<?> getModelBaseClass();
    
    
    public Model getModel(){
        return model;
    }

    
    @Override
    public Object getValue(Item item, int columnIndex) {
        return getModel().getColumns().get(columnIndex).getValue(item);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public boolean setValue(Item item, int columnIndex, Object value) {
        if (!isReadOnly()) {
            StoreColumn<Item,Object> column = (StoreColumn<Item,Object>) this.getModel().getColumns().get(columnIndex);
            return column.setValue(item, value);
        }
        return false;
    }

    @Override
    public String getDisplayValue(Item item, int columnIndex) {
        return getModel().getColumns().get(columnIndex).getDisplayValue(item);
    }

    @Override
    public void setDisplayValue(Item item, int columnIndex, String value) throws ValidationException {
        getModel().getColumns().get(columnIndex).setDisplayValue(item, value);
    }

    @Override
    public void setDisplayValues(Item item, String... valueList) throws ValidationException {
        for (int i=0; i<valueList.length; i++){
            setDisplayValue(item, i, valueList[i]);
        }
    }

    @Override
    public void refreshColumnValues(int columnIndex) {
        getModel().getColumns().get(columnIndex).refreshValues();
    }

    @Override
    public Item addNew() {
        return addNew(size());
    }

    @Override
    public Item addNew(int rowIndex) {
        if (!isReadOnly()){
            Item item = getModel().createItem();
            add(rowIndex, item);
            return item;
        }
        return null;
    }

    @Override
    public Item findByDisplayValues(String... columnValues) {
        return findByDisplayValues(0, columnValues);
    }

    @Override
    public Item findByDisplayValues(int startColumnIndex, String... columnValues) {
        int index = indexOfDisplayValues(startColumnIndex, columnValues);
        return index == -1 ? null : get(index);
    }

    @Override
    public int indexOfDisplayValues(String... columnValues) {
        return indexOfDisplayValues(0, columnValues);
    }

    @Override
    public int indexOfDisplayValues(int startColumnIndex, String... columnValues) {
        int index = 0;
        int columnIndex = startColumnIndex;
        
        if (columnValues.length > 0){
            MainLoop:
                
            for (Item item : this){
                for (String itemKey : columnValues){
                    String displayValue = getDisplayValue(item, columnIndex);
            
                    if (itemKey != displayValue 
                            && (itemKey == null || !itemKey.equals(displayValue))){
                        index++;
                        continue MainLoop;
                    }
                    columnIndex++;
                }
                return index;
            }
        }
        return -1;
    }

    @Override
    public boolean addListener(StoreListener<Item> listener) {
        boolean added = super.addListener(listener);
        return modelListenerList.add(listener) || added;
    }

    @Override
    public boolean removeListener(AttachListener<?> listener) {
        boolean removed = super.removeListener(listener);
        return modelListenerList.remove(listener) || removed;
    }

    @Override
    public boolean hasListener(AttachListener<?> listener) {
        return super.hasListener(listener) || modelListenerList.contains(listener);
    }
   
    @Override
    public boolean addListener(ModelListener<Item> listener) {
        return modelListenerList.add(listener);
    }

    @Override
    public Set<ModelListener<Item>> getModelListeners() {
        return modelListenerList;
    }

    @Override
    public boolean hasParent(View view) {
        return getParents().contains(view);
    }

    @Override
    public boolean registerParent(View view) {
        return getParents().add(view);
    }

    @Override
    public boolean unregisterParent(View view) {
        return getParents().remove(view);
    }
    
    @Override
    public Set<View> getParents() {
        if (widgetList == null){
            widgetList = new SharedList<View>(){

                @Override
                protected void onAfterAdd(int index, View item) {
                    getModel().onRegisterParent(item, true);
                }

                @Override
                protected void onAfterRemove(int index, View item) {
                    getModel().onRegisterParent(item, false);
                }

                @Override
                protected void onBeforeReplace(int index, View oldItem, View newItem) throws AttachException {
                    onAfterRemove(index, oldItem);
                }
                
                @Override
                protected void onAfterReplace(int index, View oldItem, View newItem) {
                    onAfterAdd(index, newItem);
                }

            };
        }
        return widgetList;
    }

}